package com.hexacta.hrs.test.data;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.vectrics.common.db.RecordObject;

/**
 * Abstract Class for TestDataCreator.
 * 
 * @see TestDataCreator
 * @author tpollak
 */
public abstract class AbstractTestDataCreator implements TestDataCreator {

    private DataUtils dataUtils;

    private final List<TestDataDependencyCreator> dependencies;

    private final DataCreatorHelper helper;

    private SessionFactory sessionFactory;

    public AbstractTestDataCreator(List<TestDataDependencyCreator> dependencies) {
        this.dependencies = Collections.unmodifiableList(dependencies);
        this.helper = new DataCreatorHelper();
    }

    public Object createTestData(Map<String, Object> context) {
        // Call the dependencies to insert the dependent data first
        List<TestDataDependencyCreator> dependenciesList = this.createDependenciesTraversalList(this);
        for (TestDataDependencyCreator dependency : dependenciesList) {
            Scenario scenario = (Scenario) (context.containsKey(SCENARIO) ? context.get(SCENARIO)
                    : Scenario.DEVELOPMENT);
            if (dependency.getApplicableScenarios().contains(scenario)) {
                dependency.createDependentTestData(context);
                this.flushSession();
            }
        }

        // Now create the test data
        return this.internalCreateTestData(context);
    }

    public DataUtils getDataUtils() {
        return this.dataUtils;
    }

    public List<TestDataDependencyCreator> getDependencies() {
        return this.dependencies;
    }

    /**
     * @return the sessionFactory
     */
    public final SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    public void setDataUtils(DataUtils dataUtils) {
        this.dataUtils = dataUtils;
    }

    /**
     * @param sessionFactory
     *            the sessionFactory to set
     */
    public final void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    protected final void flushSession() {
        new HibernateTemplate(this.getSessionFactory()).flush();
    }

    protected final Session getCurrentSession() {
        return this.getSessionFactory().getCurrentSession();
    }

    protected abstract Object internalCreateTestData(Map<String, Object> context);

    protected final Object loadObjectFromContext(Map<String, Object> context, String key) {
        return this.getHelper().loadObjectFromContext(context, key, this.getCurrentSession());
    }

    protected final void storeObjectReferenceInContext(Map<String, Object> context, String key, RecordObject object) {
        this.getHelper().storeObjectReferenceInContext(context, key, object);
    }

    /**
     * This method creates a List of dependencies, obtained by traversing the
     * dependency graph in post-order, and guaranteeing that each dependency
     * will be included in the list only once.
     */
    private List<TestDataDependencyCreator> createDependenciesTraversalList(AbstractTestDataCreator creator) {
        List<TestDataDependencyCreator> dependencies = new ArrayList<TestDataDependencyCreator>();
        List<TestDataDependencyCreator> unprocessedDependencies = new ArrayList<TestDataDependencyCreator>(creator
                .getDependencies());
        while (!unprocessedDependencies.isEmpty()) {
            TestDataDependencyCreator unprocessed = unprocessedDependencies.remove(0);
            // Remove duplicates, leaving the lowest one
            if (dependencies.contains(unprocessed)) {
                dependencies.remove(unprocessed);
            }
            dependencies.add(0, unprocessed);
            unprocessedDependencies.addAll(unprocessed.getDependencies());
        }
        return dependencies;
    }

    private DataCreatorHelper getHelper() {
        return this.helper;
    }
}